# -*- coding: utf-8 -*-
"""流水线监控器"""
import threading
import time
import gc
from typing import Dict, List
from flask import current_app
from app.core.test_suite_selector import TestSuiteSelector
from app.core.jenkins_job_manager import JenkinsJobManager


class PipelineMonitor:
    """流水线监控器，负责管理每个设备的持续运行状态"""

    def __init__(self, app=None):
        self.app = app
        self._monitors = {}  # 存储设备监控信息
        self._monitor_threads = {}  # 存储监控线程
        self._stop_events = {}  # 存储停止事件
        self.config = {}  # 存储配置信息
        self.suite_selector = TestSuiteSelector()
        self.job_manager = JenkinsJobManager()
        self._lock = threading.RLock()  # 添加锁以保护共享资源
        
        if app is not None:
            self.init_app(app)

    def _safe_log(self, level: str, message: str):
        """安全的日志记录方法，处理没有应用上下文的情况"""
        try:
            if level == 'info':
                current_app.logger.info(message)
            elif level == 'error':
                current_app.logger.error(message)
            elif level == 'warning':
                current_app.logger.warning(message)
            elif level == 'debug':
                current_app.logger.debug(message)
            else:
                current_app.logger.info(message)
        except RuntimeError:
            # 如果没有应用上下文，使用标准的print输出
            print(f"[{level.upper()}] {message}")

    def init_app(self, app):
        """初始化应用"""
        self.app = app
        self.config = {
            'JENKINS_JOB_NAME': app.config['JENKINS_JOB_NAME'],
            'CHECK_INTERVAL': app.config.get('CHECK_INTERVAL', 10)
        }
        self.job_manager.init_app(app)
        
        # 移除teardown_appcontext注册，避免每次请求后清理监控
        # 监控应该只在明确调用stop时才停止

    def _get_latest_strategy(self, device_name: str) -> Dict:
        """获取设备最新的策略配置"""
        from app.api.routes.test_strategy import get_device_active_strategy
        from flask import g, json
        
        try:
            with self.app.test_request_context():
                g.skip_auth = True
                response, status_code = get_device_active_strategy(device_name)
                
                if hasattr(response, 'get_json'):
                    response_data = response.get_json()
                else:
                    response_data = json.loads(response.data.decode('utf-8'))
                
                if not isinstance(response_data, dict):
                    return None
                
                if response_data.get('code') != 200:
                    return None
                
                strategy = response_data.get('data', {}).get('data')
                if not strategy:
                    return None
                
                # 添加设备型号的节点信息
                device_model = strategy.get('device_model', {})
                strategy['node'] = device_model.get('node', 'KAOJI1')
                
                return strategy
        except Exception as e:
            self._safe_log('error', f"获取策略失败: {str(e)}")
            return None

    def _process_monitor_cycle(self, device_name: str) -> None:
        """处理单个监控周期"""
        with self._lock:
            if device_name not in self._monitors:
                return
            
            monitor_info = self._monitors.get(device_name, {})
            current_build = monitor_info.get('current_build')
            last_check_time = monitor_info.get('last_check_time', 0)
            
            # 添加时间间隔检查，避免频繁检查
            current_time = time.time()
            if current_time - last_check_time < self.config.get('CHECK_INTERVAL', 10):
                return
            
            # 更新最后检查时间
            self._monitors[device_name]['last_check_time'] = current_time
        
        # 每次循环都获取最新策略
        strategy = self._get_latest_strategy(device_name)
        if not strategy:
            return
        
        # 构建测试套件数据
        daily_plans = strategy.get('daily_plans', [])
        strategy_data = {
            'DEVICE_NAME': device_name,
            'NODE': strategy.get('node', 'KAOJI1'),  # 添加节点信息
            'IS_TRAVERSE_RESOLUTION': str(
                strategy.get('is_traverse_resolution', False)
            ).lower(),
            'DESCRIPTION': strategy.get('description', ''),
            'test_suites': {
                f'星期{plan["weekday"] + 1}': (
                    f"{plan['test_suite_name']}_{plan['test_suite_id']}"
                )
                for plan in daily_plans
            },
            'trigger_time': ','.join(f'星期{plan["weekday"]+1}' for plan in daily_plans)
        }

        # 检查当前构建状态
        if current_build is not None:
            status = self.job_manager.get_build_status(current_build)
            
            # 如果构建已完成，检查是否需要启动新构建
            if not self._should_continue_monitoring(status):
                current_strategy_to_run = self.suite_selector.get_current_strategy(
                    [strategy_data]
                )
                if current_strategy_to_run:
                    # 检查是否有其他构建正在运行
                    if not self._has_running_build(device_name):
                        self._start_new_build(device_name, current_strategy_to_run)
        else:
            # 没有运行中的构建，检查是否需要启动新构建
            current_strategy_to_run = self.suite_selector.get_current_strategy(
                [strategy_data]
            )
            if current_strategy_to_run:
                # 检查是否有其他构建正在运行
                if not self._has_running_build(device_name):
                    self._start_new_build(device_name, current_strategy_to_run)

    def _should_continue_monitoring(self, status: str) -> bool:
        """判断是否继续监控当前构建"""
        if status is False:
            return False
        return status not in ['SUCCESS', 'FAILURE', 'ABORTED']

    def _start_new_build(self, device_name: str, strategy: Dict) -> None:
        """启动新的构建"""
        result = self.job_manager.execute_job(strategy, device_name)
        if result.get('status') == 'success':
            new_build = result.get('build_number')
            if new_build is not None:
                with self._lock:
                    if device_name in self._monitors:
                        self._monitors[device_name].update({
                            'current_build': new_build
                        })

    def _has_running_build(self, device_name: str) -> bool:
        """检查设备是否有正在运行的构建"""
        try:
            job_name = self.config.get('JENKINS_JOB_NAME')
            job_info = self.job_manager.job_service.jenkins_client.client.get_job_info(job_name)
            
            for build in job_info.get('builds', [])[:10]:  # 只检查最近的10个构建
                try:
                    build_info = self.job_manager.job_service.jenkins_client.client.get_build_info(
                        job_name, 
                        build['number']
                    )
                    if build_info['building']:
                        params = {}
                        for action in build_info['actions']:
                            if isinstance(action, dict) and 'parameters' in action:
                                params = {p['name']: p['value'] for p in action['parameters']}
                                break
                            
                        if params.get('DEVICE_NAME') == device_name:
                            return True
                except Exception as e:
                    self._safe_log('error', f"检查构建 #{build['number']} 状态时发生错误: {str(e)}")
                
            return False
        except Exception as e:
            self._safe_log('error', f"检查运行中构建时发生错误: {str(e)}")
            return False

    def _monitor_pipeline(self, device_name: str, stop_event: threading.Event) -> None:
        """监控流水线的核心逻辑"""
        with self.app.app_context():
            try:
                check_interval = self.config.get('CHECK_INTERVAL', 10)
                error_count = 0
                max_errors = 3

                while not stop_event.is_set():
                    try:
                        self._process_monitor_cycle(device_name)
                        error_count = 0  # 重置错误计数
                        
                        # 使用更短的间隔进行停止检查，提高响应性
                        for _ in range(check_interval):
                            if stop_event.is_set():
                                break
                            time.sleep(1)
                    except Exception as e:
                        error_count += 1
                        current_app.logger.error(f"设备 {device_name} 监控周期发生错误: {str(e)}")
                        
                        # 如果连续错误过多，暂停更长时间
                        if error_count > max_errors:
                            current_app.logger.warning(f"设备 {device_name} 连续错误过多，暂停监控60秒")
                            stop_event.wait(60)
                            error_count = 0  # 重置错误计数
                        else:
                            stop_event.wait(check_interval)
                            
                        # 主动触发垃圾回收，防止内存泄漏
                        gc.collect()
            except Exception as e:
                current_app.logger.error(f"设备 {device_name} 监控线程异常终止: {str(e)}")
            finally:
                with self._lock:
                    self._cleanup_device(device_name)
                current_app.logger.info(f"设备 {device_name} 监控线程已终止")

    def start_monitor(self, device_name: str, strategies_data: List[Dict]) -> None:
        """启动对特定设备的监控"""
        if not device_name:
            return

        try:
            with self._lock:
                if device_name in self._monitor_threads:
                    self.stop_monitor(device_name)

                # 创建停止事件
                stop_event = threading.Event()
                self._stop_events[device_name] = stop_event

                # 初始化监控信息
                self._monitors[device_name] = {
                    'current_build': None
                }

                # 创建并启动监控线程
                monitor_thread = threading.Thread(
                    target=self._monitor_pipeline,
                    args=(device_name, stop_event),
                    name=f"monitor_{device_name}"
                )
                monitor_thread.daemon = True
                self._monitor_threads[device_name] = monitor_thread
                monitor_thread.start()
                
                self._safe_log('info', f"设备 {device_name} 监控已启动")

        except Exception as e:
            self._safe_log('error', f"启动设备 {device_name} 监控失败: {str(e)}")
            with self._lock:
                self._cleanup_device(device_name)

    def stop_monitor(self, device_name: str) -> None:
        """停止对特定设备的监控"""
        with self._lock:
            if device_name in self._stop_events:
                self._stop_events[device_name].set()
                
        # 线程等待不应该在锁内执行，避免死锁
        if device_name in self._monitor_threads:
            try:
                # 给线程足够的时间完成清理任务
                self._monitor_threads[device_name].join(timeout=5)
                if self._monitor_threads[device_name].is_alive():
                    self._safe_log('warning', f"设备 {device_name} 监控线程未能在5秒内终止")
            except Exception as e:
                self._safe_log('error', f"等待设备 {device_name} 监控线程终止时出错: {str(e)}")
        
        with self._lock:
            self._cleanup_device(device_name)
            
        # 主动触发垃圾回收
        gc.collect()
        self._safe_log('info', f"设备 {device_name} 监控已停止")

    def stop_all(self) -> None:
        """停止所有监控"""
        # 复制设备名列表，避免在迭代过程中修改字典
        with self._lock:
            device_names = list(self._monitors.keys())
            
        for device_name in device_names:
            self.stop_monitor(device_name)
            
        # 确保所有资源被清理
        with self._lock:
            self._monitors.clear()
            self._monitor_threads.clear()
            self._stop_events.clear()
            
        # 主动触发垃圾回收
        gc.collect()
        self._safe_log('info', "所有设备监控已停止")

    def _cleanup_device(self, device_name: str) -> None:
        """清理设备相关的资源"""
        if device_name in self._monitors:
            self._monitors.pop(device_name, None)
        if device_name in self._monitor_threads:
            self._monitor_threads.pop(device_name, None)
        if device_name in self._stop_events:
            self._stop_events.pop(device_name, None)

    def get_status(self) -> Dict:
        """获取监控状态"""
        with self._lock:
            return {
                device_name: {
                    'current_build': info.get('current_build'),
                    'is_running': device_name in self._monitor_threads and 
                                 self._monitor_threads[device_name].is_alive()
                }
                for device_name, info in self._monitors.items()
            }
